/**
 * @brief 进程池，主进程通过管道来控制多个子进程完成命令
 * 体现了了进程间通信的发送消息和进程控制的功能
*/

#include <iostream>
#include <string>
#include <ctime>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;

constexpr int PROCESS_NUM = 5; // fork的子进程数
using func_t = void (*)();

// 任务表中的内容
void downloadTask() {
    cout << getpid() << ": 执行下载任务..." << endl << endl;
    sleep(1);
}

void ioTask() {
    cout << getpid() << ": 执行io任务..." << endl << endl;
    sleep(1);
}

void flushTask() {
    cout << getpid() << ": 执行刷新任务..." << endl << endl;
    sleep(1);
}

/**
 * @brief 把需要子进程执行的任务加载到任务表中
 * @param task_table 输出型参数，把任务加载到其中
*/
void loadTaskFunc(vector<func_t>& task_table) {
    task_table.push_back(downloadTask);
    task_table.push_back(ioTask);
    task_table.push_back(flushTask);
}

/**
 * @brief 记录子进程的信息，方便给父进程调用
*/
struct SubEndPoint {
public:
    pid_t _sub_pid; // 子进程的pid
    string _name; // 子进程的名字，用于标识分辨
    int _write_fd; // 记录父进程与该子进程进行进程间通信的信道的写端fd
    static int _num; // 记录一共有多少个子进程

public:
    SubEndPoint(pid_t sub_pid, int write_fd) noexcept
        : _sub_pid(sub_pid)
        , _write_fd(write_fd)
    {
        char name_buffer[1024] = {};
        snprintf(name_buffer, sizeof name_buffer, "process-%d[pid(%d)-fd(%d)]", _num++, _sub_pid, _write_fd);
        _name += name_buffer;
    }
};
int SubEndPoint::_num = 0; //类里面只是声明，外面才是定义

/**
 * @brief 创建随机数种子
*/
inline void makeSeed() {
    // 随机数种子随便写，让其更加随机(笑)
    srand(static_cast<unsigned int>(time(nullptr)) ^ 0X114514 & 0X5959870 | 0153721);
}

/**
 * @brief 发送任务码
 * @param aim_process 接受任务码的进程的信息
 * @param task_index 任务码
*/
void sendTaskCode(const SubEndPoint& aim_process, int task_index) {
    cout << "send task num: " << task_index << " send to -> " << aim_process._name << endl;
    int n = write(aim_process._write_fd, &task_index, sizeof(task_index));
    assert(n == sizeof(task_index));
    (void)n;
}

/**
 * @brief 接受任务码
 * @param read_fd 子进程与父进程联系的管道的读端fd
 * @return 接受到的任务码
*/
int recieveTaskCode(int read_fd) {
    int code = 0;
    ssize_t size = read(read_fd, &code, sizeof(code));
    if (size == 4) { // 读到4个字节说明成功读到了任务码，返回。注：write和read都是使用字节流(二进制)
        return code;
    }
    else { // 写端关闭
        return -1;
    }
}

/**
 * @brief 创建子进程，并让子进程等待父进程给出的信号然后做出反应
 * @param subs 输出型参数，用于存储所有子进程的信息，并返回给父进程调用
 * @param task_table 任务表，子进程根据父进程给出的命令码，从中选取任务执行
*/
void creatSubProcess(vector<SubEndPoint>& subs, const vector<func_t>& task_table) {
    // 因为需要创建多个子进程并且每个子进程都需要与父进程建立一个管道
    // 所以每次创建一个新的子进程时，在该子进程前创立的子进程与父进程的管道的写端都会由父进程继承给该子进程
    // 因此新的子进程会有旧子进程管道的写端fd，这本该由父进程独有
    // 所以需要把父进程与所有子进程的写端fd记录下来，然后每一个新建的子进程都要把与旧子进程管道的写端fd关掉
    // 否则管道在关闭时会有bug，类似于shared_ptr的循环引用计数，只不过这里不循环，只是连成了链表
    // 所以关闭管道时是从最后创建的管道先关闭，解除了对前面的引用计数后，再依此向前关闭
    vector<int> delete_fd;

    for (int i = 0; i < PROCESS_NUM; ++i) {
        int fds[2];
        int n = pipe(fds);
        assert(n == 0);
        (void)n; // 因为assert只在debug模式下起作用，所以在release版本中，n会因为没有被使用而报警告，这里时为了让编译器不要报警

        pid_t pid = fork();
        if (pid == 0) { // 子进程
            for (const auto& fd : delete_fd) { // 结束新子进程与就子进程的关系(引用计数)
                close(fd);
            }

            close(fds[1]); // 关闭写端
            while (true) {
                // 1、获取任务码
                int task_code = recieveTaskCode(fds[0]);

                // 2、获取到命令码的执行任务，没获取到的继续在1、中阻塞时等待
                if (task_code >= 0 && task_code < task_table.size()) {
                    task_table[task_code](); // 通过函数指针完成函数回调
                }
                else { // 父进程写端关了，子进程读端也该关了，并且结束子进程
                    break;
                }
            }

            // 当父进程的写端关闭时子进程会退出循环
            close(fds[0]); // 关闭读端
            exit(0);
        }

        // 父进程
        close(fds[0]);
        subs.push_back(SubEndPoint(pid, fds[1]));
        delete_fd.push_back(fds[1]);
    }
}

void loadbalanceControl(const vector<SubEndPoint>& subs, const vector<func_t>& task_table, int task_count) {
    int process_num = subs.size(); // 有多少个子进程
    int task_num = task_table.size(); // 有多少个任务
    bool forever = (task_count == 0 ? true : false);

    while (forever || task_count--) {
        // 1、随机选择一个子进程
        int sub_index = rand() % process_num;

        // 2、随机选择一个任务
        int task_index = rand() % task_num;

        // 3、发送任务码给子进程
        sendTaskCode(subs[sub_index], task_index);
    }

    // 发送任务结束，关闭所有管道写端
    for (const auto& sub_ed : subs) {
        close(sub_ed._write_fd);
    }
}

/**
 * @brief 等待子进程关闭并回收资源
*/
void waitProcess(const vector<SubEndPoint>& subs) {
    for (const auto& sub_ed : subs) {
        waitpid(sub_ed._sub_pid, nullptr, 0); // 不要退出码
        cout << "wait sub process success ...: " << sub_ed._sub_pid << endl;
    }

}

int main(int, char**) {
    makeSeed();

    // 1、加载方法表，子进程从方法表中选取任务来执行
    vector<func_t> task_table;
    loadTaskFunc(task_table);

    // 2、创建子进程，并维护好父子进程通信的信道，让子进程等待父进程的信号，并对信号做出反应
    vector<SubEndPoint> subs;
    creatSubProcess(subs, task_table);

    // 3、控制子进程，负载均衡的向子进程发送命令码
    int task_count = 1008611; // 记录父进程要向子进程发送多少次任务，当task_count == 0时则为一直发送
    loadbalanceControl(subs, task_table, task_count);

    // 4、等待子进程退出并回收资源
    waitProcess(subs);

    return 0;
}